package com.faxsun.core.util.service;

import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.broadleafcommerce.common.time.SystemTime;
import org.broadleafcommerce.common.util.StreamCapableTransactionalOperationAdapter;
import org.broadleafcommerce.common.util.StreamingTransactionCapableUtil;
import org.broadleafcommerce.core.order.domain.Order;
import org.broadleafcommerce.core.order.service.OrderService;
import org.broadleafcommerce.core.util.service.type.PurgeCartVariableNames;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.faxsun.core.order.domain.FSOrderImpl;
import com.faxsun.core.order.service.type.FSInternalOrderStatus;
import com.faxsun.core.order.service.type.FSOrderStatus;
import com.faxsun.core.util.dao.FSResourcePurgeDao;

/**
 * 关闭未支付订单定时任务
 * 
 * TODO:考虑库存回滚
 * 
 * @author zhaoteng.song@faxsun.com
 *
 */
@Service("fsCloseOrderService")
public class FSOrderCloseService {

	private static final Log LOG = LogFactory.getLog(FSOrderCloseService.class);

	private static final long TWENTI_SIX_HOUR = 26L * 60 * 60 * 1000;

	@Resource(name = "blStreamingTransactionCapableUtil")
	protected StreamingTransactionCapableUtil transUtil;

	@Resource(name = "fsResourcePurgeDao")
	protected FSResourcePurgeDao resourcePurgeDao;

	@Resource(name = "blOrderService")
	protected OrderService orderService;

	protected Integer pageSize = 10;

	@PostConstruct
	public void init() {
		if (pageSize != null) {
			transUtil.setPageSize(pageSize);
		}
	}

	/**
	 * 关闭订单
	 * 
	 * @param config
	 *            订单参数
	 */
	@Transactional("blTransactionManager")
	public void closeOrder(final Map<String, String> config) {
		if (LOG.isDebugEnabled()) {
			LOG.debug("Close orders");
		}
		if (MapUtils.isEmpty(config)) {
			throw new IllegalArgumentException(
					"Cannot purge carts since there was no configuration provided. "
							+ "In the absence of config params, all carts would be candidates for deletion.");
		}
		try {
			// The removal will be performed in chunks based on page size. This
			// minimizes transaction times.
			transUtil.runStreamingTransactionalOperation(
					new StreamCapableTransactionalOperationAdapter() {
						@Override
						public void pagedExecute(Object[] param)
								throws Throwable {
							@SuppressWarnings("unchecked")
							List<Order> orders = (List<Order>) param[0];
							for (Order cart : orders) {
								closeOrderAction(cart);
							}
						}

						@Override
						public Object[] retrievePage(int startPos, int pageSize) {
							List<Order> results = getOrdersToPurge(config,
									startPos, pageSize);
							return new Object[] { results };
						}

						@Override
						public Long retrieveTotalCount() {
							return getOrdersToPurgeLength(config);
						}

						@Override
						public boolean shouldRetryOnTransactionLockAcquisitionFailure() {
							return true;
						}
					}, RuntimeException.class);
		} catch (Exception e) {
			LOG.error("Unable to purge carts in FSResourcePurgeService", e);
		}
	}

	/**
	 * 关闭订单逻辑
	 *
	 * @param cart
	 *            the cart to remove
	 */
	@Transactional("blTransactionManager")
	protected void closeOrderAction(Order order) {
		try {
			// 确认订单状态为已提交且未支付
			FSOrderImpl fsOrder = (FSOrderImpl) order;
			if (FSOrderStatus.SUBMITTED.equals(fsOrder.getStatus())
					&& FSInternalOrderStatus.PAYMENT_SUBMITTED.equals(fsOrder
							.getInternalStatus())) {
				Date createdDate = fsOrder.getAuditable().getDateCreated();
				Date updatedDate = fsOrder.getAuditable().getDateUpdated();
				long start = createdDate.getTime();

				if (updatedDate != null) {
					start = updatedDate.getTime();
				}

				if (System.currentTimeMillis() - start > TWENTI_SIX_HOUR) {
					// 设置为订单取消
					order.setStatus(FSOrderStatus.CANCELLED);
					//TODO: 记录修改时间
					LOG.warn("cancel the order"+fsOrder.getId());
					this.orderService.save(fsOrder, false);
				}
			}
		} catch (Exception e) {
			LOG.error("Unable to purge a cart", e);
		}
	}

	private class CartPurgeParams {

		private Map<String, String> config;
		private String[] nameArray;
		private FSOrderStatus[] statusArray;
		private FSInternalOrderStatus[] internalStatusArray;
		private Date dateCreatedMinThreshold;
		private Boolean isPreview;

		public CartPurgeParams(Map<String, String> config) {
			this.config = config;
		}

		public String[] getNameArray() {
			return nameArray;
		}

		public FSOrderStatus[] getStatusArray() {
			return statusArray;
		}

		public FSInternalOrderStatus[] getInternalStatusArray() {
			return internalStatusArray;
		}

		public Date getDateCreatedMinThreshold() {
			return dateCreatedMinThreshold;
		}

		public Boolean getIsPreview() {
			return isPreview;
		}

		public CartPurgeParams invoke() {
			nameArray = null;
			statusArray = null;
			dateCreatedMinThreshold = null;
			isPreview = null;
			for (Map.Entry<String, String> entry : config.entrySet()) {
				if (PurgeCartVariableNames.STATUS.toString().equals(
						entry.getKey())) {
					String[] temp = entry.getValue().split(",");
					statusArray = new FSOrderStatus[temp.length];
					int index = 0;
					for (String name : temp) {
						FSOrderStatus orderStatus = FSOrderStatus
								.getInstance(name);
						statusArray[index] = orderStatus;
						index++;
					}
				}

				if ("INTERNAL_STATUS".equals(entry.getKey())) {
					String[] temp = entry.getValue().split(",");
					internalStatusArray = new FSInternalOrderStatus[temp.length];
					int index = 0;
					for (String name : temp) {
						FSInternalOrderStatus orderStatus = FSInternalOrderStatus
								.getInstance(name);
						internalStatusArray[index] = orderStatus;
						index++;
					}
				}

				if (PurgeCartVariableNames.NAME.toString().equals(
						entry.getKey())) {
					nameArray = entry.getValue().split(",");
				}
				if (PurgeCartVariableNames.SECONDS_OLD.toString().equals(
						entry.getKey())) {
					Long secondsOld = Long.parseLong(entry.getValue());
					dateCreatedMinThreshold = new Date(SystemTime.asMillis()
							- (secondsOld * 1000));
				}
				if (PurgeCartVariableNames.IS_PREVIEW.toString().equals(
						entry.getKey())) {
					isPreview = Boolean.parseBoolean(entry.getValue());
				}
			}
			return this;
		}
	}

	/**
	 * Get the list of carts to delete from the database. Subclasses may
	 * override for custom cart retrieval logic.
	 *
	 * @param config
	 *            params for the query
	 * @return list of carts to delete
	 */
	protected List<Order> getOrdersToPurge(Map<String, String> config,
			int startPos, int length) {
		CartPurgeParams purgeParams = new CartPurgeParams(config).invoke();
		String[] nameArray = purgeParams.getNameArray();
		FSOrderStatus[] statusArray = purgeParams.getStatusArray();
		FSInternalOrderStatus[] internalStatusArray = purgeParams
				.getInternalStatusArray();
		Date dateCreatedMinThreshold = purgeParams.getDateCreatedMinThreshold();
		Boolean isPreview = purgeParams.getIsPreview();
		return resourcePurgeDao.findOrders(nameArray, statusArray,
				internalStatusArray, dateCreatedMinThreshold, isPreview,
				startPos, length);
	}

	/**
	 * Get the count of carts to delete from the database. Subclasses may
	 * override for custom cart retrieval logic.
	 *
	 * @param config
	 *            params for the query
	 * @return count of carts to delete
	 */
	protected Long getOrdersToPurgeLength(Map<String, String> config) {
		CartPurgeParams purgeParams = new CartPurgeParams(config).invoke();
		String[] nameArray = purgeParams.getNameArray();
		FSOrderStatus[] statusArray = purgeParams.getStatusArray();
		FSInternalOrderStatus[] internalStatusArray = purgeParams
				.getInternalStatusArray();
		Date dateCreatedMinThreshold = purgeParams.getDateCreatedMinThreshold();
		Boolean isPreview = purgeParams.getIsPreview();
		return resourcePurgeDao.findOrdersCount(nameArray, statusArray,
				internalStatusArray, dateCreatedMinThreshold, isPreview);
	}
}
